/* Shared memory use like transport layer */
/* Each shared memory block uses two sems (of the same set) with the same
 * key as the memory block. One to block critical pieces (index 0) and
 * the other to tell if data new data is present (index 1) */
/* Each memory is managed as a queue, defined by Tque that is in
 * the last part of the memory */
/* Each message is sent and received is a Tmsg that contains its size */

/* Notes: Upon exit() all attached shared memory segments are detached from the process. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "../../../clsv/error.h"
#include "../../../clsv/ipc/sem.h"
#include "../../../clsv/ipc/shm.h"
#include "shmADT.h"

/*--------------------------------------------------------------------------*/

#define SHM_MSG_CANT	1024
#define SHM_MSG_SIZE	1025
#define SHM_HEAD_SIZE	sizeof(Tque)

/* Size only of the queue part */
#define SHM_QUE	(SHM_MSG_CANT * SHM_MSG_SIZE)
/* Total size, queue + header */
#define SHM_SIZE	(SHM_QUE + SHM_HEAD_SIZE)

#define SHM_PERMS_SND	0666
#define SHM_PERMS_RCV	0666
#define SHM_PERMS_SEM	0666

#define SHM_SEM_COUNT	2
#define SHM_SEM_BLOCK	0	/* Sem use for blocking */
#define SHM_SEM_WAIT	1	/* Sem use for wait, ready */

/* Initial message, connection identifier */
#define SHM_CONN 1026
#define SHM_DISC 1027
#define SHM_TIMEOUT	10

/*--------------------------------------------------------------------------*/

struct shmCDT
{
	int keySnd;	/* Shared memory and semaphore key for sending */
	int keyRcv;	/* Shared memory and semaphore key for receiving */
	int shmIdSnd; /* Shared memory id for sending */
	int shmIdRcv; /* Shared memory id for receiving */
	int semIdSnd; /* Semaphore id for sending */
	int semIdRcv; /* Semaphore id for receiving */
	void *shmPtrSnd; /* Send memory pointer */
	void *shmPtrRcv; /* Receive memory pointer */
	char server; /* To tell if it is server or not */
};

/* Shared memory message queue */
typedef struct Tque
{
	int head;
	int tail;
	int size;
} Tque;

typedef struct Tmsg
{
	int size; /* Message type */
	char msg[SHM_MSG_SIZE]; /* Message text */
} Tmsg;

typedef struct Tkeys
{
	int keySnd;
	int keyRcv;
} Tkeys;

/*--------------------------------------------------------------------------*/

static int initShmADT(int keySnd, int keyRcv, shmADT *msg);

static int initCliShm(shmADT shm);

static int initSrvShm(shmADT shm);

int rcvMemQue(void *mem, void *buf, int count);

int sndMemQue(void *mem, void *buf, int count);

/*--------------------------------------------------------------------------*/

extern int errno;
extern int counter;
void * globa, * globa2;

/*--------------------------------------------------------------------------*/

/* Common initialization of shmADT */
static int
initShmADT(int keySnd, int keyRcv, shmADT *shm)
{
	shmADT aux;

	*shm = NULL;

	if ( (aux = malloc(sizeof(struct shmCDT)) ) == NULL )
	{
		fprintf(stderr,"shmADT: Initiating shmADT, malloc error.\n");
		return ERROR_MALLOC; /* Not enough memory */
	}

	aux->keySnd = keySnd;
	aux->keyRcv = keyRcv;
	aux->server = 0;

	*shm = aux;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/
static int
initCliShm(shmADT shm)
{
	void *ptr;
	int ans;
	int error = ERROR_OK;

	/* Open initial shared memory */
	/* To Send */
	ans = openMemShmRW(shm->keySnd, SHM_SIZE, &ptr);
	if( ans < 0 )
		return ans;
	shm->shmIdSnd = ans;
	shm->shmPtrSnd = ptr;
	/* To receive */
	ans = openMemShmRW(shm->keyRcv, SHM_SIZE, &ptr);
	if( ans < 0 )
	{
		delOpenedMemShm(shm->shmPtrSnd);
		return ans;
	}
	shm->shmIdRcv = ans;
	shm->shmPtrRcv = ptr;

	/* Initiate sems */
	/* To send */
	error = openSem(shm->keySnd, SHM_SEM_COUNT);
	if( error < 0 )
	{
		delOpenedMemShm(shm->shmPtrSnd);
		delOpenedMemShm(shm->shmPtrRcv);
		return error;
	}
	shm->semIdSnd = error;
	/* To receive */
	error = openSem(shm->keyRcv, SHM_SEM_COUNT);
	if( error < 0 )
	{
		delOpenedMemShm(shm->shmPtrSnd);
		delOpenedMemShm(shm->shmPtrRcv);
		return error;
	}
	shm->semIdRcv = error;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

int
connShm(int keySnd, int keyRcv, shmADT *shm)
{
	int buf;
	Tkeys keys;
	int error = ERROR_OK;

	/* Common initialization */
	error = initShmADT(keySnd, keyRcv, shm);
	if( error != ERROR_OK )
		return error;

	/* Open initial set of shared memories ans sems */
	error = initCliShm(*shm);
	if( error != ERROR_OK )
		return error;

	/* Send initial message */
	buf = SHM_CONN;
	error = sndShm(*shm, &buf, sizeof(buf));
	if( error != sizeof(buf) )
	{
		fprintf(stderr,"shmADT: Connecting, error sending initial message.\n");
		return error;
	}

	/* Receive new shared mem */
	error = rcvShm(*shm, &keys, sizeof(keys));
	if( error != sizeof(keys) )
	{
		fprintf(stderr,"shmADT: Connecting, error receiving initial message.\n");
		return error;
	}

	(*shm)->keySnd = keys.keySnd;
	(*shm)->keyRcv = keys.keyRcv;

//	printf("Mis SHM 2: Rcv= (%d), Rcv= (%d)\n", (*shm)->keyRcv, (*shm)->keySnd);

	/* Close previous shared mem */
	delOpenedMemShm((*shm)->shmPtrSnd);
	delOpenedMemShm((*shm)->shmPtrRcv);

	/* Open new set of shared memories ans sems */
	error = initCliShm(*shm);
	if( error != ERROR_OK )
		return error;

	return error;
}

/*--------------------------------------------------------------------------*/

static int
initSrvShm(shmADT shm)
{
	Tque *que;
	int ans;
	void *ptr;
	int error = ERROR_OK;

	/* Create for sending */
	ans = createMemShmRW(shm->keySnd, SHM_SIZE, SHM_PERMS_SND, &ptr);
	if( ans < 0 )
		return ans;
	shm->shmIdSnd = ans;
	shm->shmPtrSnd = ptr;

	/* Create for reading */
	ans = createMemShmRW(shm->keyRcv, SHM_SIZE, SHM_PERMS_RCV, &ptr);
	if( ans < 0 )
	{
		delCreatedMemShm(shm->shmIdSnd, shm->shmPtrSnd);
		return ans;
	}
	shm->shmIdRcv = ans;
	shm->shmPtrRcv = ptr;

	/* Initialise queue */
	que = (Tque *)((char *)(shm->shmPtrSnd) + SHM_QUE);
	que->head = 0;
	que->tail = 0;
	que->size = 0;
	que = (Tque *)((char *)(shm->shmPtrRcv) + SHM_QUE);
	que->head = 0;
	que->tail = 0;
	que->size = 0;

	/* Initiate sems */
	error = createSem(shm->keySnd, SHM_SEM_COUNT, SHM_PERMS_SEM);
	if( error < 0 )
	{
		delCreatedMemShm(shm->shmIdSnd, shm->shmPtrSnd);
		delCreatedMemShm(shm->shmIdRcv, shm->shmPtrRcv);
		return error;
	}
	shm->semIdSnd = error;
	error = createSem(shm->keyRcv, SHM_SEM_COUNT, SHM_PERMS_SEM);
	if( error < 0 )
	{
		delSem(shm->semIdSnd);
		delCreatedMemShm(shm->shmIdSnd, shm->shmPtrSnd);
		delCreatedMemShm(shm->shmIdRcv, shm->shmPtrRcv);
		return error;
	}
	shm->semIdRcv = error;

	shm->server = 1;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

/* Create a new queue to start listening */
int
bindShm(int keySnd, int keyRcv, shmADT *shm)
{
	int error = ERROR_OK;

	/* Common initialization */
	error = initShmADT(keySnd, keyRcv, shm);
	if( error != ERROR_OK )
		return error;

	return initSrvShm(*shm);

}

/*--------------------------------------------------------------------------*/

int
acceptShm(shmADT shm, shmADT *new)
{
	Tkeys keys;
	int newKeySnd, newKeyRcv;
	int buf;
	int error = ERROR_OK;

	/* Receive new client */
	error = rcvTShm(shm, &buf, sizeof(buf), 0);
	if( error != sizeof(buf) )
	{
		fprintf(stderr,"shmADT: Accepting, error receiving initial message.\n");
		return ERROR_UNEX;
	}

	/* Get new keys */
	newKeySnd = shm->keySnd + counter*2 +1;
	newKeyRcv = shm->keyRcv + counter*2 +1;

	/* Common initialization */
	error = initShmADT(newKeySnd, newKeyRcv, new);
	if( error != ERROR_OK )
		return error;

	/* Open new mems ans sems */
	error = initSrvShm(*new);
	if( error != ERROR_OK )
		return error;

	/* Send to write where server is receiving */
	keys.keySnd = newKeyRcv;
	/* Send to receive where server is writting */
	keys.keyRcv = newKeySnd;

	/* Send new keys */

	error = sndShm(shm, &keys, sizeof(keys));

printf("KeySnd= (%d), KeyRcv= (%d)\n", newKeySnd, newKeyRcv);

	if( error != sizeof(keys) )
	{
		delCreatedMemShm((*new)->shmIdRcv, (*new)->shmPtrRcv);
		delCreatedMemShm((*new)->shmIdSnd, (*new)->shmPtrSnd);
		delSem((*new)->semIdSnd);
		delSem((*new)->semIdRcv);
		fprintf(stderr,"shmADT: Accepting, error sending initial message.\n");
		return ERROR_UNEX;
	}

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

int
rcvMemQue(void *mem, void *buf, int count)
{
	Tque *que;
	Tmsg *msg;
	int size;

	que = (Tque *)((char *)mem + SHM_QUE);
	if( que->size <= 0)
	{
		fprintf(stderr,"shmADT: Receiving error, trying to read empty memory.\n");
		size = ERROR_EOF;
	}
	else
	{
		msg = (Tmsg *)((char *)mem + que->head*SHM_MSG_SIZE);
		if( count >= msg->size )
			size = msg->size;
		else
			size = count;
		
		globa2= msg->msg;
		
		memcpy( buf, msg->msg, size);
		if( que->head >= SHM_MSG_CANT - 1)
		{	que->head = 0;	}
		else
		{	(que->head)++;	}
		(que->size)--;
	}
	return size;
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
int
rcvTShm(shmADT shm, void *buf, int count, int timeout)
{
	int size;
	int error = ERROR_OK;

	/* Check parameters */
	/* Count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	if(count > SHM_MSG_SIZE)
		return ERROR_MAX;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Data wait sem */

//	printf("Estoy esperando el semaforo (%d). Mi Semaforo de SND es (%d)\n", shm->semIdRcv, shm->semIdSnd);

	error = waitSem(shm->semIdRcv, SHM_SEM_WAIT, timeout);

	if( error != ERROR_OK )
	{
//		if( error == ERROR_TIMEOUT && timeout )
//			fprintf(stderr,"shmADT: Timeout while receiving.\n");
		return error;
	}
//	printf("Recibi un mensaje!\t");
	/* Block memory */
	error = pSem(shm->semIdRcv, SHM_SEM_BLOCK, timeout);
	if( error != ERROR_OK )
	{
		if( error == ERROR_TIMEOUT && timeout )
			fprintf(stderr,"shmADT: Timeout while receiving.\n");
		return error;
	}

//	printf("Lo leo de id= (%d). Escribo en id= (%d)\n", shm->shmIdRcv, shm->shmIdSnd);

	size = rcvMemQue(shm->shmPtrRcv, buf, count);

	/* Unblock memory */
	error = vSem(shm->semIdRcv, SHM_SEM_BLOCK);
	if( error != ERROR_OK )
		return error;

	return size;
}

/*--------------------------------------------------------------------------*/

int
rcvShm(shmADT shm, void *buf, int count)
{
	return rcvTShm(shm, buf, count, SHM_TIMEOUT);
}

/*--------------------------------------------------------------------------*/

int
sndMemQue(void *mem, void *buf, int count)
{
	Tque *que;
	Tmsg *msg;
	int size;

	que = (Tque *)((char *)mem + SHM_QUE);
	if( que->size >= SHM_MSG_CANT)
	{
		fprintf(stderr,"shmADT: Sending error, trying to write to a full memory.\n");
		size = ERROR_MAX;
	}
	else
	{
		msg = (Tmsg *)((char *)mem + que->tail*SHM_MSG_SIZE);
		if( count > SHM_MSG_SIZE )
			size = SHM_MSG_SIZE;
		else
			size = count;
			
			globa= msg->msg;
			
		memcpy( msg->msg, buf, count);
		msg->size = count;
		if( que->tail >= SHM_MSG_CANT - 1)
		{	que->tail = 0;}
		else
		{	(que->tail)++;	}
		(que->size)++;
	}

	return size;

}

/*--------------------------------------------------------------------------*/

/* Returns 0, less if error */
int
sndTShm(shmADT shm, void *buf, int count, int timeout)
{
	int size;
	int error = ERROR_OK;

	/* Check parameters */
	/* Count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	if(count > SHM_MSG_SIZE)
		return ERROR_MAX;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Block memory */
	error = pSem(shm->semIdSnd, SHM_SEM_BLOCK, timeout);

	if( error != ERROR_OK )
	{
		if( error == ERROR_TIMEOUT && timeout )
			fprintf(stderr,"shmADT: Timeout while sending.\n");
		return error;
	}
//printf("Mando un mensaje a (%p). Recibo de (%d)\n", shm->semIdSnd, shm->semIdRcv);
	size = sndMemQue(shm->shmPtrSnd, buf, count);

	/* Unblock memory */
	error = vSem(shm->semIdSnd, SHM_SEM_BLOCK);

	if( error != ERROR_OK )
		return error;

	/* Data ready sem */
	error = readySem(shm->semIdSnd, SHM_SEM_WAIT);
	if( error != ERROR_OK )
		return error;

	return size;
}

/*--------------------------------------------------------------------------*/

int
sndShm(shmADT shm, void *buf, int count)
{
	return sndTShm(shm, buf, count, 0);
}

/*--------------------------------------------------------------------------*/

int
closeShm(shmADT shm)
{
	int buf, ans;
	int error = ERROR_OK;

	if( shm != NULL )
	{
		if( shm->server )
		{
			ans = rcvShm(shm, &buf, sizeof(buf));
			error = delCreatedMemShm(shm->shmIdRcv, shm->shmPtrRcv);
			error = delCreatedMemShm(shm->shmIdSnd, shm->shmPtrSnd);

			error = delSem(shm->semIdSnd);
			error = delSem(shm->semIdRcv);
		}
		else
		{
			buf = SHM_DISC;
			ans = sndShm(shm, &buf, sizeof(buf));
			error = detachShm(shm->shmPtrSnd);
			error = detachShm(shm->shmPtrRcv);
		}

		free(shm);
	}
	return error;
}

/*--------------------------------------------------------------------------*/
